/************************************************
*               global-functions                *
************************************************/
function isArray(obj) { return Array.isArray(obj); }
function numeric(obj) { return Number.isFinite(obj); }
function isString(obj) { return typeof obj === 'string'; }
function isBoolean(obj) { return typeof obj === 'boolean'; }
function isFunc(obj) { return typeof obj === 'function'; }
function isEmpty(obj) {
    if (Object.is(obj, null) || Object.is(obj, undefined) || Object.is(obj, NaN) || Object.is(obj, '')) return true;
    else if (Array.isArray(obj) && obj.length === 0) return true;
    else return (typeof obj === 'object' && Object.keys(obj).length <= 0);
}
function isDefined(name) { return env[name] !== undefined; }
function notEmpty(obj) { return !isEmpty(obj); }
function notEmptyArr(obj) { return Array.isArray(obj) && obj.length > 0; }
function nowTimeStr(noSplit = false) {
    let date = new Date();
    let [m, d, h, min, s] = [date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()].map(item => (item < 10 ? '0' : '') + item);
    return noSplit ? `${date.getFullYear()}${m}${d}_${h}${min}${s}` : `${date.getFullYear()}-${m}-${d} ${h}:${min}:${s}`;
}
function isColor(color) { return Math.notNegtive(color) && color <= 0xFFFFFF; }
let env = window;
env.isArray = isArray;
env.numeric = numeric;
env.isString = isString;
env.isBoolean = isBoolean;
env.isFunc = isFunc;
env.isEmpty = isEmpty;
env.isDefined = isDefined;
env.notEmpty = notEmpty;
env.notEmptyArr = notEmptyArr;
env.nowTimeStr = nowTimeStr;
env.isColor = isColor;
/************************************************
*                   Object                      *
************************************************/
Object.values = function (obj) { return Object.keys(obj).map(function (key) { return obj[key]; }); }
Object.deepGet = function (obj, ...paths) {
    if (isEmpty(obj)) return null;
    let res = obj;
    for (let property of paths) {
        if (isEmpty(res[property])) return res[property];
        else res = res[property];
    }
    return res;
}
Object.clone = function (obj) { return Object.assign(Object.create(Object.getPrototypeOf(obj)), obj); }
Object.deepClone = function (obj) {
    let _obj = null;
    if (isArray(obj)) {
        _obj = [];
        for (let item of obj) { _obj.push(Object.deepClone(item)); }
        return _obj;
    } else if (typeof obj === 'object') {
        if (obj === null) return null;
        _obj = {};
        for (let prop in obj) {
            if (obj.hasOwnProperty(prop)) _obj[prop] = Object.deepClone(obj[prop]);
        }
        return _obj;
    } else return obj;
}
Object.defineProperty(Object.prototype, 'hasFunc', {
    value: function (name) { return isFunc(this[name]); },
    enumerable: false,
});
Object.defineProperty(Object.prototype, 'doFunc', {
    value: function (name, ...params) {
        if (this.hasFunc(name)) return Reflect.apply(this[name], this, params);
        else return null;
    },
    enumerable: false,
});
/************************************************
*                   Array                       *
************************************************/
Array.fill = function (value, len = 1) { return new Array(len).fill(value); }
Array.nature = function (to, from = 1, step = 1) {
    let temp = Array.from({ length: to - from + 1 }, (v, k) => k + from);
    if (step === 1) return temp;
    else return temp.filter(v => (v - from) % step === 0);
}
/************************************************/
Array.prototype.contains = Array.prototype.includes;
Array.prototype.containsInsOf = function (clazz) { return this.some(obj => obj instanceof clazz); }
Array.prototype.excludes = function (value) { return !this.includes(value); };
Array.prototype.all = function (value) { return this.every(function (val) { return Object.is(value, val); }); }
Array.prototype.notAll = function (value) { return !this.all(value); };
Array.prototype.head = function (num = 1) { if (num === 1) return this[0]; else return this.part(0, num); }
Array.prototype.tail = function (num = 1) { if (num === 1) return this[this.length - 1]; else return this.part(-num); }
Array.prototype.circleAt = function (index) { if (this.length <= 0) return null; else return this[index % this.length]; }
Array.prototype.random = function (except = null) {
    if (isEmpty(this)) return null;
    let arr = this;
    if (!!except) arr = [...this].remove(except);
    if (isEmpty(arr)) return null;
    else return arr[Math.randomInt(0, arr.length - 1)];
}
Array.prototype.unique = function () {
    let set = new Set(this);
    this.delete(0).push(...Array.from(set));
    return this;
}
Array.prototype.exchange = function (idx1, idx2) {
    let [_i1, _i2] = [idx1 >= 0 ? idx1 : this.length + idx1, idx2 >= 0 ? idx2 : this.length + idx2];
    if (_i1 !== _i2)[this[_i1], this[_i2]] = [this[_i2], this[_i1]];
    return this;
}
Array.prototype.delete = function (startIndex, len = this.length - startIndex) {
    this.splice(startIndex, len);
    return this;
}
Array.prototype.insert = function (startIndex, valueOrArr) {
    if (isArray(valueOrArr)) this.splice(startIndex, 0, ...valueOrArr);
    else this.splice(startIndex, 0, valueOrArr);
    return this;
}
Array.prototype.remove = function (...valuesOrArrList) {
    let params = [...valuesOrArrList].reduce((linearArr, value) => {
        isArray(value) ? linearArr.push(...value) : linearArr.push(value);
        return linearArr;
    }, []);
    for (let i = 0; i < this.length; i++) {
        if (params.includes(this[i])) {
            this.splice(i, 1);
            i--;
        }
    }
    return this;
}
Array.prototype.pushUnique = function (...values) {
    this.push(...values.filter(value => this.excludes(value)));
    return this.length;
}
Array.prototype.unshiftUnique = function (...values) {
    this.unshift(...values.filter(value => this.excludes(value)));
    return this.length;;
}
Array.prototype.shuffle = function () {
    this.forEach((val, i) => this.exchange(i, Math.randomInt(0, this.length - 1)));
    return this;
}
Array.prototype.circleShuffle = function () {
    let [start, len] = [Math.randomInt(0, this.length - 1), this.length];
    return this.insert(len, this).delete(0, start).delete(len, len - start);
}
Array.prototype.merge = function (...arrList) { return Array.from([this, ...arrList]).reduce((merged, arr) => [...merged, ...arr], []); }
Array.prototype.union = function (...arrList) { return Array.from(new Set(this.merge(...arrList))); }
Array.prototype.intersect = function (arr) { return [...this].unique().filter(value => arr.includes(value)); }
Array.prototype.difference = function (arr) { return [...this].unique().filter(value => arr.excludes(value)); }
Array.prototype.part = function (start, len = this.length - start) { return this.slice(start, start + len + (start >= 0 ? 0 : this.length)); }
Array.prototype.asc = function () { return this.sort((a, b) => a - b); }
Array.prototype.desc = function () { return this.asc().reverse(); }
Array.prototype.sortBy = function (...paths) {
    this.sort((v1, v2) => getProperty(v1) - getProperty(v2));
    function getProperty(v) {
        let _v = v;
        for (let path of [...paths]) {
            _v = _v[path];
            if (!_v) return null;
        }
        return _v;
    }
    return this;
}
/************************************************
*                   String                      *
************************************************/
String.prototype.padRight = function (len, padding) {
    let rest = len - this.length;
    if (rest <= 0) return this.substr(0, len);
    else if (!isString(padding) || isEmpty(padding)) return this.concat();
    else return this.concat(padding.repeat(Math.ceil(rest / padding.length)).substr(0, rest));
}
String.prototype.padLeft = function (len, padding) {
    let rest = len - this.length;
    if (rest <= 0) return this.substr(0, len);
    else if (!isString(padding) || isEmpty(padding)) return this.concat();
    else return padding.repeat(Math.ceil(rest / padding.length)).substr(0, rest).concat(this);
}
/************************************************
*                       Math                    *
************************************************/
Math.randomInt = function (from, to) {
    if (!numeric(from) || !numeric(to)) return NaN;
    let [floor, ceil] = [Math.ceil(from), Math.floor(to)];
    if (floor > ceil) return NaN;
    return Math.floor(Math.random() * (ceil - floor + 1)) + floor;
}
Math.randomBool = function (percent = 0.5) { return Math.random() < percent; }
Math.angleToRadian = function (angle) { return angle * Math.PI / 180; }
Math.radianToAngle = function (radian) { return radian * 180 / Math.PI; }
Math.sum = function (...nums) { return nums.reduce((pre, cur) => pre + cur, 0); }
Math.avg = function (...nums) { return Math.sum(...nums) / nums.length; }
Math.isPositive = function (num) { return numeric(num) && num > 0; }
Math.notNegtive = function (num) { return numeric(num) && num >= 0; }
Math.isPosInt = function (num) { let reg = /^[1-9]+[0-9]*]*$/; return reg.test(num); }
